﻿#region Copyright (c) 2011, thinktecture (http://www.thinktecture.com)

/*
   thinktecture.UIAnnotations was developed by Jörg Neumann.
   Copyright (c) 2011, thinktecture (http://www.thinktecture.com).
   All rights reserved, comes as-is and without any warranty. Use of this
   source file is governed by the license which is contained in LICENSE.TXT 
   in the distribution.
*/

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Thinktecture.UIAnnotations
{
    public class Annotations : FrameworkElement
    {

        #region Private Variables

        private static Annotations _current;
        private static TraceSource _traceSource;
        private static Dictionary<WeakReference, CueBannerAdorner> _cueBannerAdorners;
        private static Dictionary<WeakReference, RequiredFieldAdorner> _requiredAdorners;
        private static Dictionary<WeakReference, ElementTrackerAdorner> _elementTrackerAdorners;
        private static Dictionary<WeakReference, GenericAdorner> _toolbarAdorners;
        private static Dictionary<WeakReference, SmartTagAdorner> _smartTagAdorners;
        private static Dictionary<WeakReference, GenericAdorner> _highlightOnFocusAdorners;
        private static Dictionary<WeakReference, EditTrackerAdorner> _editTrackerAdorners;
        private DoubleAnimation _editorToolbarBlendInAnimation;
        private DoubleAnimation _editorToolbarBlendOutAnimation;
        private TimeSpan _editorToolbarBlendTimeSpan = new TimeSpan(0, 0, 0, 0, 300);
        private List<WeakReference> _showDescriptionControls = new List<WeakReference>();
        private ContentControl _showDescriptionTarget;

        #endregion

        #region Constructors

        static Annotations()
        {
            _traceSource = new TraceSource("Thinktecture.UIAnnotations");
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_Ctor, "Ctor");

            CueBannerProperty = DependencyProperty.RegisterAttached(
                "CueBanner", typeof(string), typeof(Annotations),
                new PropertyMetadata("", OnCueBannerChanged));
            IsRequiredProperty = DependencyProperty.RegisterAttached(
                "IsRequired", typeof(bool), typeof(Annotations), 
                new PropertyMetadata(false, OnIsRequiredChanged));
            RequiredImageAlignmentProperty = DependencyProperty.RegisterAttached(
                "RequiredImageAlignment", typeof(ExtensionImageAlignment), typeof(Annotations),
                new PropertyMetadata(ExtensionImageAlignment.Right, OnRequiredImageAlignmentChanged));
            TrackerImageAlignmentProperty = DependencyProperty.RegisterAttached(
                "TrackerImageAlignment", typeof(ExtensionImageAlignment), typeof(Annotations),
                new PropertyMetadata(ExtensionImageAlignment.Left, OnTrackerImageAlignmentChanged));
            ShowElementTrackerProperty = DependencyProperty.RegisterAttached(
                "ShowElementTracker", typeof(bool), typeof(Annotations),
                new PropertyMetadata(false, OnShowElementTrackerChanged));
            AttachErrorTemplateProperty = DependencyProperty.RegisterAttached(
                "AttachErrorTemplate", typeof(bool), typeof(Annotations),
                new PropertyMetadata(false, OnAttachErrorTemplateChanged));
            AttachEditorToolbarProperty = DependencyProperty.RegisterAttached(
                "AttachEditorToolbar", typeof(bool), typeof(Annotations),
                new PropertyMetadata(false, OnAttachEditorToolbarChanged));
            AttachSmartTagProperty = DependencyProperty.RegisterAttached(
                "AttachSmartTag", typeof(bool), typeof(Annotations),
                new PropertyMetadata(false, OnAttachSmartTagChanged));
            SmartTagMenuProperty = DependencyProperty.RegisterAttached(
                "SmartTagMenu", typeof(ContextMenu), typeof(Annotations),
                new PropertyMetadata(null, OnSmartTagMenuChanged));
            AttachEditContextMenuProperty = DependencyProperty.RegisterAttached(
                "AttachEditContextMenu", typeof(bool), typeof(Annotations),
                new PropertyMetadata(false, OnAttachEditContextMenuChanged));
            HighlightOnFocusProperty = DependencyProperty.RegisterAttached(
                "HighlightOnFocus", typeof(bool), typeof(Annotations),
                new PropertyMetadata(false, OnHighlightOnFocusChanged));
            ShowEditTrackerProperty = DependencyProperty.RegisterAttached(
                "ShowEditTracker", typeof(bool), typeof(Annotations),
                new PropertyMetadata(false, OnShowEditTrackerChanged));
            ApplyDataAnnotationsProperty = DependencyProperty.RegisterAttached(
                "ApplyDataAnnotations", typeof(bool), typeof(Annotations),
                new PropertyMetadata(false, OnApplyDataAnnotationsChanged));
            DescriptionDisplayControlProperty = DependencyProperty.RegisterAttached(
                "DescriptionDisplayControl", typeof(FrameworkElement), typeof(Annotations),
                new PropertyMetadata(null, OnDescriptionDisplayControlChanged));
            DescriptionDefaultTextProperty = DependencyProperty.RegisterAttached(
                "DescriptionDefaultText", typeof(string), typeof(Annotations),
                new PropertyMetadata("", OnDescriptionDefaultTextChanged));
            _cueBannerAdorners = new Dictionary<WeakReference, CueBannerAdorner>();
            _requiredAdorners = new Dictionary<WeakReference, RequiredFieldAdorner>();
            _elementTrackerAdorners = new Dictionary<WeakReference, ElementTrackerAdorner>();
            _toolbarAdorners = new Dictionary<WeakReference, GenericAdorner>();
            _smartTagAdorners = new Dictionary<WeakReference, SmartTagAdorner>();
            _highlightOnFocusAdorners = new Dictionary<WeakReference, GenericAdorner>();
            _editTrackerAdorners = new Dictionary<WeakReference, EditTrackerAdorner>();
        }
        
        #endregion

        #region Dependency Properties

        public static readonly DependencyProperty CueBannerProperty;
        public static readonly DependencyProperty IsRequiredProperty;
        public static readonly DependencyProperty RequiredImageAlignmentProperty;
        public static readonly DependencyProperty ShowElementTrackerProperty;
        public static readonly DependencyProperty TrackerImageAlignmentProperty;
        public static readonly DependencyProperty AttachErrorTemplateProperty;
        public static readonly DependencyProperty AttachEditorToolbarProperty;
        public static readonly DependencyProperty AttachSmartTagProperty;
        public static readonly DependencyProperty SmartTagMenuProperty;
        public static readonly DependencyProperty AttachEditContextMenuProperty;
        public static readonly DependencyProperty HighlightOnFocusProperty;
        public static readonly DependencyProperty ShowEditTrackerProperty;
        public static readonly DependencyProperty ApplyDataAnnotationsProperty;
        public static readonly DependencyProperty DescriptionDisplayControlProperty;
        public static readonly DependencyProperty DescriptionDefaultTextProperty;

        #endregion

        #region Private Properties

        private static Annotations Current
        {
            get
            {
                if (_current == null)
                    _current = new Annotations();
                return _current;
            }
        }

        #endregion

        #region Private Methods

        private void RemoveElement(DependencyObject obj)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_RemoveElement, "{0}", TraceHelpers.GetElementName(obj));
            Adorner adorner;
            WeakReference weakRef;
            IDictionary[] adornerStore = GetAdornerDictionaries();
            IDictionary dict = GetAdornerDict(obj, adornerStore, out weakRef, out adorner);
            if (dict != null && weakRef != null)
            {
                if (adorner != null)
                {
                    AdornerLayer layer = AdornerLayer.GetAdornerLayer(adorner);
                    if (layer != null)
                        layer.Remove(adorner);
                    else
                        TraceHelpers.TraceWarning(_traceSource, TraceId.Annotations_RemoveElement, "Can't remove adorner \"{0}\"!", adorner.ToString());
                    IDisposable disposable = adorner as IDisposable;
                    if (disposable != null)
                        disposable.Dispose();
                }
                else
                    TraceHelpers.TraceWarning(_traceSource, TraceId.Annotations_RemoveElement, "Can't get adorner for element \"{0}\"!", obj.ToString());
                dict.Remove(weakRef);
            }
            else
                TraceHelpers.TraceWarning(_traceSource, TraceId.Annotations_RemoveElement, "Can't find adorner dict for \"{0}\"!", obj.ToString());

            // Dispose instance
            DisposeCurrent();
        }

        private static void DisposeCurrent()
        {
            IDictionary[] adornerStore = GetAdornerDictionaries();
            bool unload = true;
            foreach (IDictionary adornerDict in adornerStore)
            {
                if (adornerDict.Keys.Count > 0)
                {
                    unload = false;
                    break;
                }
            }
            if (unload)
            {
                _current = null;
                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_DisposeCurrent, "Current disposed.");
            }
        }

        private static IDictionary[] GetAdornerDictionaries()
        {
            return new IDictionary[]
                {
                    _cueBannerAdorners,
                    _requiredAdorners,
                    _elementTrackerAdorners,
                    _toolbarAdorners,
                    _smartTagAdorners,
                    _highlightOnFocusAdorners,
                    _editTrackerAdorners,
                };
        }

        private static IDictionary GetAdornerDict(DependencyObject obj, IDictionary[] dicts, out WeakReference weakRef, out Adorner adorner)
        {
            IDictionary adornerDict = null;
            weakRef = null;
            adorner = null;
            if (obj == null) return null;
            List<WeakReference> removeList = new List<WeakReference>();
            foreach (IDictionary dict in dicts)
            {
                foreach (WeakReference wr in dict.Keys)
                {
                    if (wr.IsAlive)
                    {
                        if (wr.Target.Equals(obj))
                        {
                            weakRef = wr;
                            adorner = dict[wr] as Adorner;
                            adornerDict = dict;
                        }
                    }
                    else
                    {
                        removeList.Add(wr);
                    }
                }
                foreach (WeakReference wr in removeList)
                {
                    TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_GetAdornerDict, "Removing adorner \"{0}\" from dictionary because associated item is disposed.", dict[wr].ToString());
                    dict.Remove(wr);
                }
            }
            
            if (adornerDict != null)
                return adornerDict;
            
            TraceHelpers.TraceWarning(_traceSource, TraceId.Annotations_GetAdorner, "Can't find adorner dictionary for element \"{0}\"!", TraceHelpers.GetElementName(obj));
            return null;
        }

        private void OnElementUnloaded(object sender, RoutedEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            element.RemoveHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(OnElementUnloaded));
            Current.RemoveElement(element);
        }

        private static Adorner GetAdorner(DependencyObject obj, IDictionary dict)
        {
            if (obj == null) return null;
            Adorner adorner = null;
            List<WeakReference> removeList = new List<WeakReference>();
            foreach (WeakReference wr in dict.Keys)
            {
                if (wr.IsAlive)
                {
                    if (wr.Target == obj)
                    {
                        adorner = (Adorner)dict[wr];
                    }
                }
                else
                {
                    removeList.Add(wr);
                }
            }
            foreach (WeakReference wr in removeList)
            {
                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_GetAdorner, "Removing adorner \"{0}\" from dictionary because associated item is disposed.", dict[wr].ToString());
                dict.Remove(wr);
            }
            
            if (adorner != null)
                return adorner;

            return null;
        }

        private static TextBox GetTextBox(object element)
        {
            if (element is TextBox)
            {
                return (TextBox)element;
            }
            else if (element is ComboBox)
            {
                ComboBox comboBox = element as ComboBox;
                if (comboBox.Template != null)
                    return comboBox.Template.FindName("PART_EditableTextBox", comboBox) as TextBox;
                else
                    TraceHelpers.TraceWarning(_traceSource, TraceId.Annotations_GetTextBox, "Can't find ComboBox editor in \"{0}\"", TraceHelpers.GetElementName(comboBox));
            }
            else if (element is DatePicker)
            {
                DatePicker picker = element as DatePicker;
                if (picker.Template != null)
                    return picker.Template.FindName("PART_TextBox", picker) as TextBox;
                else
                    TraceHelpers.TraceWarning(_traceSource, TraceId.Annotations_GetTextBox, "Can't find DatePicker editor in \"{0}\"", TraceHelpers.GetElementName(picker));
            }
            return null;
        }

        #endregion

        #region CueBanner Property

        private static void OnCueBannerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "CueBanner for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                string cueBanner = (string)e.NewValue;
                CueBannerAdorner adn = GetAdorner(d, _cueBannerAdorners) as CueBannerAdorner;
                if (adn != null)
                {
                    adn.CueBanner = cueBanner;
                }
                else
                {
                    if (element.IsLoaded)
                        Current.OnCueBannerLoaded(element, new RoutedEventArgs());
                    else
                        element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnCueBannerLoaded));
                }
            }
        }
        
        private void OnCueBannerLoaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "CueBanner for \"{0}\"", TraceHelpers.GetElementName(sender));
            UIElement element = sender as UIElement;
            CueBannerAdorner adorner = new CueBannerAdorner(element, GetCueBanner(element));
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
            layer.Add(adorner);
            _cueBannerAdorners.Add(new WeakReference(element), adorner);
            element.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(Current.OnElementUnloaded));
        }

        public static void SetCueBanner(UIElement element, string value)
        {
            element.SetValue(CueBannerProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static string GetCueBanner(UIElement element)
        {
            return (string)element.GetValue(CueBannerProperty);
        }

        #endregion

        #region IsRequired Property
        
        private static void OnIsRequiredChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "IsRequired for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                bool isRequired = (bool)e.NewValue;
                RequiredFieldAdorner adn = GetAdorner(d, _requiredAdorners) as RequiredFieldAdorner;
                if (adn != null)
                {
                    adn.IsRequired = isRequired;
                }
                else
                {
                    if (element.IsLoaded)
                        Current.OnIsRequiredLoaded(element, new RoutedEventArgs());
                    else
                        element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnIsRequiredLoaded));
                }
            }
        }

        private void OnIsRequiredLoaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "IsRequired for \"{0}\"", TraceHelpers.GetElementName(sender));
            UIElement element = sender as UIElement;
            RequiredFieldAdorner adorner = new RequiredFieldAdorner(element, GetIsRequired(element), GetRequiredImageAlignment(element));
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
            layer.Add(adorner);
            _requiredAdorners.Add(new WeakReference(element), adorner);
            element.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(Current.OnElementUnloaded));
        }

        public static void SetIsRequired(UIElement element, bool value)
        {
            element.SetValue(IsRequiredProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetIsRequired(UIElement element)
        {
            return (bool)element.GetValue(IsRequiredProperty);
        }

        #endregion

        #region RequiredImageAlignment Property

        private static void OnRequiredImageAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "RequiredImageAlignment for \"{0}\"", TraceHelpers.GetElementName(d));
            RequiredFieldAdorner adn = GetAdorner(d, _requiredAdorners) as RequiredFieldAdorner;
            if (adn != null)
            {
                adn.Alignment = (ExtensionImageAlignment)e.NewValue;
            }
        }

        public static void SetRequiredImageAlignment(UIElement element, ExtensionImageAlignment value)
        {
            element.SetValue(RequiredImageAlignmentProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static ExtensionImageAlignment GetRequiredImageAlignment(UIElement element)
        {
            return (ExtensionImageAlignment)element.GetValue(RequiredImageAlignmentProperty);
        }

        #endregion

        #region ShowElementTracker Property

        private static void OnShowElementTrackerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "ShowElementTracker for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                bool showTracker = (bool)e.NewValue;
                ElementTrackerAdorner adn = GetAdorner(d, _elementTrackerAdorners) as ElementTrackerAdorner;
                if (adn != null)
                {
                    adn.ShowTracker = showTracker;
                }
                else
                {
                    if (element.IsLoaded)
                        Current.OnShowElementTrackerLoaded(element, new RoutedEventArgs());
                    else
                        element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnShowElementTrackerLoaded));
                }
            }
        }

        private void OnShowElementTrackerLoaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "ShowElementTracker for \"{0}\"", TraceHelpers.GetElementName(sender));
            UIElement element = sender as UIElement;
            ElementTrackerAdorner adorner = new ElementTrackerAdorner(element, GetShowElementTracker(element), GetTrackerImageAlignment(element));
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
            layer.Add(adorner);
            _elementTrackerAdorners.Add(new WeakReference(element), adorner);
            element.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(Current.OnElementUnloaded));
        }

        public static void SetShowElementTracker(UIElement element, bool value)
        {
            element.SetValue(ShowElementTrackerProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetShowElementTracker(UIElement element)
        {
            return (bool)element.GetValue(ShowElementTrackerProperty);
        }

        #endregion

        #region TrackerImageAlignment Property

        private static void OnTrackerImageAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "TrackerImageAlignment for \"{0}\"", TraceHelpers.GetElementName(d));
            ElementTrackerAdorner adn = GetAdorner(d, _elementTrackerAdorners) as ElementTrackerAdorner;
            if (adn != null)
            {
                if (adn != null && e.NewValue != null)
                    adn.Alignment = (ExtensionImageAlignment)e.NewValue;
            }
        }

        public static void SetTrackerImageAlignment(UIElement element, ExtensionImageAlignment value)
        {
            element.SetValue(TrackerImageAlignmentProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static ExtensionImageAlignment GetTrackerImageAlignment(UIElement element)
        {
            return (ExtensionImageAlignment)element.GetValue(TrackerImageAlignmentProperty);
        }

        #endregion

        #region AttachErrorTemplate Property

        private static void OnAttachErrorTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "AttachErrorTemplate for \"{0}\"", TraceHelpers.GetElementName(d));
            bool attach = (bool)e.NewValue;
            if (attach)
            {
                Uri resourceLocater =
                    new Uri("/Thinktecture.UIAnnotations;component/Templates.xaml", UriKind.Relative);
                ResourceDictionary dict = Application.LoadComponent(resourceLocater) as ResourceDictionary;
                object template = dict["ErrorTemplate"];
                if (template != null && template is ControlTemplate)
                {
                    TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "AttachErrorTemplate for \"{0}\"", TraceHelpers.GetElementName(d));
                    ControlTemplate errorTemplate = template as ControlTemplate;
                    Validation.SetErrorTemplate(d, errorTemplate);
                }
            }
            else
            {
                Validation.SetErrorTemplate(d, null);
            }
        }

        public static void SetAttachErrorTemplate(UIElement element, bool value)
        {
            element.SetValue(AttachErrorTemplateProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetAttachErrorTemplate(UIElement element)
        {
            return (bool)element.GetValue(AttachErrorTemplateProperty);
        }

        #endregion

        #region AttachEditorToolbar Property

        private static void OnAttachEditorToolbarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "AttachEditorToolbar for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                bool attach = (bool)e.NewValue;
                GenericAdorner adn = GetAdorner(d, _toolbarAdorners) as GenericAdorner;
                if (adn != null)
                {
                    adn.Visible = attach;
                }
                else
                {
                    if (element.IsLoaded)
                        Current.OnEditorToolbarLoaded(element, new RoutedEventArgs());
                    else
                        element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnEditorToolbarLoaded));
                }
            }
        }

        private void OnEditorToolbarLoaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "EditorToolbar for \"{0}\"", TraceHelpers.GetElementName(sender));
            UIElement element = sender as UIElement;
            Uri resourceLocater =
                new Uri("/Thinktecture.UIAnnotations;component/Templates.xaml", UriKind.Relative);
            ResourceDictionary dict = Application.LoadComponent(resourceLocater) as ResourceDictionary;
            ControlTemplate template = dict["EditorToolbarTemplate"] as ControlTemplate;
            Control ctl = new Control();
            ctl.Template = template;
            ctl.AddHandler(Control.LoadedEvent, new RoutedEventHandler(this.OnEditorToolbarControlLoaded));
            
            GenericAdorner adorner = new GenericAdorner(element, ctl, true, AdornerVerticalAlignment.Top, AdornerHorizontalAlignment.Right, AdornerPlacement.VerticalOuterHorizontalInner, new Thickness(0, 0, 0, 2), true);
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
            layer.Add(adorner);
            _toolbarAdorners.Add(new WeakReference(element), adorner);
            element.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(Current.OnElementUnloaded));
        }

        private void OnEditorToolbarControlLoaded(object sender, RoutedEventArgs e)
        {
            Control ctl = sender as Control;
            Button bt = ctl.Template.FindName("PART_DeleteItem", ctl) as Button;
            bt.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.OnEditorToolbarDeleteButtonClicked));

            ToolBar toolBar = ctl.Template.FindName("PART_Toolbar", ctl) as ToolBar;
            if (toolBar != null)
            {
                toolBar.AddHandler(UIElement.MouseEnterEvent, new MouseEventHandler(toolBar_MouseEnter));
                toolBar.AddHandler(UIElement.MouseLeaveEvent, new MouseEventHandler(toolBar_MouseLeave));
            }
            _editorToolbarBlendInAnimation = new DoubleAnimation(1, new Duration(_editorToolbarBlendTimeSpan));
            _editorToolbarBlendOutAnimation = new DoubleAnimation(0.5, new Duration(_editorToolbarBlendTimeSpan));
        }

        void toolBar_MouseEnter(object sender, MouseEventArgs e)
        {
            ToolBar toolBar = sender as ToolBar;
            toolBar.BeginAnimation(ToolBar.OpacityProperty, _editorToolbarBlendInAnimation);
        }

        void toolBar_MouseLeave(object sender, MouseEventArgs e)
        {
            ToolBar toolBar = sender as ToolBar;
            toolBar.BeginAnimation(ToolBar.OpacityProperty, _editorToolbarBlendOutAnimation);
        }

        private void OnEditorToolbarDeleteButtonClicked(object sender, RoutedEventArgs e)
        {
            ICommandSource element = sender as ICommandSource;
            if (element != null)
            {
                TextBox txt = GetTextBox(element.CommandTarget);
                if (txt != null)
                {
                    txt.Clear();
                }
            }
        }

        private void OnDeleteButtonClick(object sender, RoutedEventArgs e)
        {
            ICommandSource element = sender as ICommandSource;
            if (element != null)
            {
                TextBox txt = GetTextBox(element.CommandTarget);
                if (txt != null)
                {
                    txt.Clear();
                }
            }
        }

        public static void SetAttachEditorToolbar(UIElement element, bool value)
        {
            element.SetValue(AttachEditorToolbarProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetAttachEditorToolbar(UIElement element)
        {
            return (bool)element.GetValue(AttachEditorToolbarProperty);
        }

        #endregion

        #region AttachSmartTag Property

        private static void OnAttachSmartTagChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "AttachSmartTag for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                bool attach = (bool)e.NewValue;
                SmartTagAdorner adn = GetAdorner(d, _smartTagAdorners) as SmartTagAdorner;
                if (adn != null)
                {
                    adn.Show = attach;
                }
                else
                {
                    if (element.IsLoaded)
                        Current.OnAttachSmartTagLoaded(element, new RoutedEventArgs());
                    else
                        element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnAttachSmartTagLoaded));
                }
            }
        }

        private void OnAttachSmartTagLoaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "AttachSmartTag for \"{0}\"", TraceHelpers.GetElementName(sender));
            UIElement element = sender as UIElement;
            Uri resourceLocater =
                new Uri("/Thinktecture.UIAnnotations;component/Templates.xaml", UriKind.Relative);
            ResourceDictionary dict = Application.LoadComponent(resourceLocater) as ResourceDictionary;
            ControlTemplate template = dict["SmartTagTemplate"] as ControlTemplate;
            ContextMenu mnu = GetSmartTagMenu(element);
            SmartTagAdorner adorner = new SmartTagAdorner(element, template, mnu);
            adorner.AddFocusHandler();
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
            layer.Add(adorner);
            _smartTagAdorners.Add(new WeakReference(element), adorner);
            element.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(Current.OnElementUnloaded));
        }

        private void OnAttachSmartTagUnloaded(object sender, RoutedEventArgs e)
        {
            this.OnElementUnloaded(sender, e);
        }

        public static void SetAttachSmartTag(UIElement element, bool value)
        {
            element.SetValue(AttachSmartTagProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetAttachSmartTag(UIElement element)
        {
            return (bool)element.GetValue(AttachSmartTagProperty);
        }

        #endregion

        #region SmartTagMenu Property

        private static void OnSmartTagMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "SmartTagMenu for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            SmartTagAdorner adn = GetAdorner(d, _smartTagAdorners) as SmartTagAdorner;
            if (element != null && adn != null)
            {
                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "SmartTagMenu for \"{0}\"", TraceHelpers.GetElementName(element));
                adn.Menu = (ContextMenu)e.NewValue;
            }
        }

        public static void SetSmartTagMenu(UIElement element, ContextMenu value)
        {
            element.SetValue(SmartTagMenuProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static ContextMenu GetSmartTagMenu(UIElement element)
        {
            return (ContextMenu)element.GetValue(SmartTagMenuProperty);
        }

        #endregion

        #region AttachEditContextMenu Property

        private static void OnAttachEditContextMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "AttachEditContextMenu for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                if (element.IsLoaded)
                    Current.OnAttachEditContextMenuLoaded(element, new RoutedEventArgs());
                else
                    element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnAttachEditContextMenuLoaded));
            }
        }

        private void OnAttachEditContextMenuLoaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "AttachEditContextMenu for \"{0}\"", TraceHelpers.GetElementName(sender));
            UIElement element = sender as UIElement;

            bool attach = (bool)GetAttachEditContextMenu(element);
            if (attach)
            {
                Uri resourceLocater =
                    new Uri("/Thinktecture.UIAnnotations;component/Templates.xaml", UriKind.Relative);
                ResourceDictionary dict = Application.LoadComponent(resourceLocater) as ResourceDictionary;
                ContextMenu menu = dict["EditContextMenu"] as ContextMenu;
                foreach (var item in menu.Items)
                {
                    if (((FrameworkElement)item).Name.Equals("PART_DeleteItem"))
                    {
                        ((MenuItem)item).AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(this.OnEditorToolbarDeleteButtonClicked));
                        break;
                    }
                }
                if (menu != null)
                {
                    TextBox txt = GetTextBox(element);
                    if (txt != null)
                    {
                        menu.DataContext = txt;
                        txt.ContextMenu = menu;
                    }
                }
            }
            else
            {
                element.SetValue(FrameworkElement.ContextMenuProperty, null);
            }
        }
        
        public static void SetAttachEditContextMenu(UIElement element, bool value)
        {
            element.SetValue(AttachEditContextMenuProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetAttachEditContextMenu(UIElement element)
        {
            return (bool)element.GetValue(AttachEditContextMenuProperty);
        }

        #endregion

        #region HighlightOnFocus Property

        private static void OnHighlightOnFocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "HighlightOnFocus for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                bool attach = (bool)e.NewValue;
                GenericAdorner adn = GetAdorner(d, _highlightOnFocusAdorners) as GenericAdorner;
                if (adn != null)
                {
                    adn.Visible = attach;
                }
                else
                {
                    if (element.IsLoaded)
                        Current.OnHighlightOnFocusLoaded(element, new RoutedEventArgs());
                    else
                        element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnHighlightOnFocusLoaded));
                }
            }
        }

        private void OnHighlightOnFocusLoaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "HighlightOnFocus for \"{0}\"", TraceHelpers.GetElementName(sender));
            UIElement element = sender as UIElement;
            Uri resourceLocater =
                new Uri("/Thinktecture.UIAnnotations;component/Templates.xaml", UriKind.Relative);
            ResourceDictionary dict = Application.LoadComponent(resourceLocater) as ResourceDictionary;
            ControlTemplate template = dict["HighlightOnFocusTemplates"] as ControlTemplate;
            Control ctl = new Control();
            ctl.Template = template;
            ctl.Height = element.RenderSize.Height;
            ctl.Width = element.RenderSize.Width;
            GenericAdorner adorner = new GenericAdorner(element, ctl, true, AdornerVerticalAlignment.Center, AdornerHorizontalAlignment.Left, AdornerPlacement.VerticalInnerHorizontalInner, new Thickness(), true);
            adorner.SetAutoSizing();
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
            layer.Add(adorner);
            _highlightOnFocusAdorners.Add(new WeakReference(element), adorner);
            element.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(Current.OnElementUnloaded));
        }

        public static void SetHighlightOnFocus(UIElement element, bool value)
        {
            element.SetValue(HighlightOnFocusProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetHighlightOnFocus(UIElement element)
        {
            return (bool)element.GetValue(HighlightOnFocusProperty);
        }

        #endregion

        #region DescriptionDisplayControl Property

        private static void OnDescriptionDisplayControlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "DescriptionDisplayControl for \"{0}\"", TraceHelpers.GetElementName(d));
            ContentControl container = d as ContentControl;
            if (container != null && (container is Window || container is UserControl))
            {
                FrameworkElement displayControl = e.NewValue as FrameworkElement;
                if (displayControl != null)
                {
                    if (container.IsLoaded)
                        Current.OnShowDescriptionWindowLoaded(d, new RoutedEventArgs());
                    else
                        container.AddHandler(Window.LoadedEvent, new RoutedEventHandler(Current.OnShowDescriptionWindowLoaded));
                }
                else
                {
                    foreach (WeakReference obj in Current._showDescriptionControls)
                    {
                        if (obj.IsAlive)
                        {
                            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_RemoveDescriptionFocusHandler, "Remove focus handler for element \"{0}\"", TraceHelpers.GetElementName(d));
                            Control ctl = obj.Target as Control;
                            ctl.RemoveHandler(Control.GotFocusEvent, new RoutedEventHandler(Current.OnShowDescriptionGotFocus));
                            ctl.RemoveHandler(Control.LostFocusEvent, new RoutedEventHandler(Current.OnShowDescriptionLostFocus));
                        }
                    }
                    Current._showDescriptionControls.Clear();
                    container.RemoveHandler(Window.LoadedEvent, new RoutedEventHandler(Current.OnShowDescriptionWindowLoaded));
                }
            }
        }

        private void OnShowDescriptionControlsUnloaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_RemoveDescriptionFocusHandler, "Remove focus handler for element \"{0}\"", TraceHelpers.GetElementName(sender));
            Control ctl = sender as Control;
            ctl.RemoveHandler(Control.GotFocusEvent, new RoutedEventHandler(Current.OnShowDescriptionGotFocus));
            ctl.RemoveHandler(Control.LostFocusEvent, new RoutedEventHandler(Current.OnShowDescriptionLostFocus));

            // Dispose instance
            DisposeCurrent();
        }

        private void OnShowDescriptionWindowLoaded(object sender, RoutedEventArgs e)
        {
            ContentControl container = sender as ContentControl;
            if (container != null)
            {
                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_ShowDescription, "Show descriptions for elements in container \"{0}\"", TraceHelpers.GetElementName(container));
                _showDescriptionTarget = container;
                this.AddFocusHandlerToChilds(container);
            }
            else
            {
                TraceHelpers.TraceWarning(_traceSource, TraceId.Annotations_ShowDescription, "Can't determine container from element \"{0}\"", TraceHelpers.GetElementName(sender));
            }
        }
        
        public void AddFocusHandlerToChilds(DependencyObject o)
        {
            if (o != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(o, i);
                    Control ctl = child as Control;
                    if (ctl != null && 
                        !(child is Label) &&
                        ctl.Focusable && 
                        !(ctl is ICommandSource))
                    {
                        TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AddDescriptionFocusHandler, "Add handler for \"{0}\"", TraceHelpers.GetElementName(ctl));
                        _showDescriptionControls.Add(new WeakReference(ctl));
                        ctl.AddHandler(Control.GotFocusEvent, new RoutedEventHandler(Current.OnShowDescriptionGotFocus));
                        ctl.AddHandler(Control.LostFocusEvent, new RoutedEventHandler(Current.OnShowDescriptionLostFocus));
                        ctl.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(Current.OnShowDescriptionControlsUnloaded));
                    }
                    if (child != null)
                    {
                        AddFocusHandlerToChilds(child);
                    }
                }
            }
        }

        private void OnShowDescriptionGotFocus(object sender, RoutedEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            if (element != null)
            {
                ContentControl target = GetDescriptionDisplayControl(_showDescriptionTarget) as ContentControl;
                if (target != null)
                    this.ShowDescription(element, target);
            }
        }

        private void OnShowDescriptionLostFocus(object sender, RoutedEventArgs e)
        {
            if (_showDescriptionTarget != null)
            {
                ContentControl target = GetDescriptionDisplayControl(_showDescriptionTarget) as ContentControl;
                if (target != null)
                    target.Content = GetDescriptionDefaultText(_showDescriptionTarget);
            }
        }

        private void ShowDescription(FrameworkElement element, ContentControl label)
        {
            if (element.TemplatedParent != null)
            {
                element = element.TemplatedParent as FrameworkElement;
            }
            FieldInfo[] properties = element.GetType().GetFields(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.FlattenHierarchy);
            foreach (FieldInfo field in properties)
            {
                if (field.FieldType == typeof(DependencyProperty))
                {
                    DependencyProperty dp = (DependencyProperty)field.GetValue(null);
                    if (BindingOperations.IsDataBound(element, dp))
                    {
                        BindingExpression exp = BindingOperations.GetBindingExpression(element, dp);
                        object source = exp.DataItem;
                        if (source != null)
                        {
                            // get the real binding source (not the view model)
                            string path;
                            source = BindingHelpers.GetRealBindingSource(exp, source, out path);
                            string description = AnnotationReader.GetAttributeValue<DisplayAttribute, string>(source.GetType(), path, "Description");
                            if (!string.IsNullOrEmpty(description))
                            {
                                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_ShowDescription, "Show description for \"{0}\"", TraceHelpers.GetElementName(element));
                                label.Content = description;
                            }
                        }
                    }
                }
            }
        }

        public static void SetDescriptionDisplayControl(UIElement element, FrameworkElement value)
        {
            element.SetValue(DescriptionDisplayControlProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(ContentControl))]
        public static FrameworkElement GetDescriptionDisplayControl(UIElement element)
        {
            return (FrameworkElement)element.GetValue(DescriptionDisplayControlProperty);
        }

        #endregion

        #region ShowEditTracker Property

        private static void OnShowEditTrackerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "ShowEditTracker for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                bool showTracker = (bool)e.NewValue;
                EditTrackerAdorner adn = GetAdorner(d, _editTrackerAdorners) as EditTrackerAdorner;
                if (adn != null)
                {
                    adn.ShowTracker = showTracker;
                }
                else
                {
                    if (element.IsLoaded)
                        Current.OnShowEditTrackerLoaded(element, new RoutedEventArgs());
                    else
                        element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnShowEditTrackerLoaded));
                }
            }
        }

        private void OnShowEditTrackerLoaded(object sender, RoutedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AdornerCreated, "ShowEditTracker for \"{0}\"", TraceHelpers.GetElementName(sender));
            UIElement element = sender as UIElement;
            EditTrackerAdorner adorner = new EditTrackerAdorner(element, GetShowEditTracker(element), GetTrackerImageAlignment(element));
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
            layer.Add(adorner);
            _editTrackerAdorners.Add(new WeakReference(element), adorner);
            element.AddHandler(FrameworkElement.UnloadedEvent, new RoutedEventHandler(Current.OnElementUnloaded));
        }

        public static void SetShowEditTracker(UIElement element, bool value)
        {
            element.SetValue(ShowEditTrackerProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetShowEditTracker(UIElement element)
        {
            return (bool)element.GetValue(ShowEditTrackerProperty);
        }

        #endregion

        #region DescriptionDefaultText Property

        private static void OnDescriptionDefaultTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "DescriptionDefaultText for \"{0}\"", TraceHelpers.GetElementName(d));
        }

        public static void SetDescriptionDefaultText(UIElement element, string value)
        {
            element.SetValue(DescriptionDefaultTextProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static string GetDescriptionDefaultText(UIElement element)
        {
            return (string)element.GetValue(DescriptionDefaultTextProperty);
        }

        #endregion

        #region ApplyDataAnnotations Property

        private static void OnApplyDataAnnotationsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d)) return;
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_AttachedPropertyChanged, "ApplyDataAnnotations for \"{0}\"", TraceHelpers.GetElementName(d));
            FrameworkElement element = d as FrameworkElement;
            if (element != null)
            {
                bool value = (bool)e.NewValue;
                if (value)
                {
                    if (element.IsLoaded)
                        Current.OnApplyDataAnnotationsLoaded(element, new RoutedEventArgs());
                    else
                        element.AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(Current.OnApplyDataAnnotationsLoaded));
                }
            }
        }
        public static void ApplyDataAnnotationsToContainer(DependencyObject container)
        {
            Current.OnApplyDataAnnotationsLoaded(container, new RoutedEventArgs());
        }

        private void OnApplyDataAnnotationsLoaded(object sender, RoutedEventArgs e)
        {
            this.ApplyDataAnnotations((DependencyObject)sender);
            TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_ApplyDataAnnotations, "Apply annotations for \"{0}\"", TraceHelpers.GetElementName(sender));
        }

        private void ApplyDataAnnotations(DependencyObject o)
        {
            if (o != null)
            {
                for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(o, i);
                    if (child is ContentPresenter)
                        child = ((ContentPresenter)child).Content as DependencyObject;
                    Control ctl = child as Control;
                    if (ctl != null &&
                        !(child is Label) &&
                        ctl.Focusable &&
                        !(typeof(Panel).IsAssignableFrom(ctl.GetType())) &&
                        !(ctl is ICommandSource))
                    {
                        this.ApplyDataAnnotationsForElement(ctl);
                    }
                    if (child != null)
                    {
                        this.ApplyDataAnnotations(child);
                    }
                }
            }
        }

        internal static void ApplyDataAnnotation(FrameworkElement element)
        {
            Current.ApplyDataAnnotationsForElement(element);
        }

        internal void ApplyDataAnnotationsForElement(FrameworkElement element)
        {
            if (element.TemplatedParent != null)
            {
                element = element.TemplatedParent as FrameworkElement;
            }
            FieldInfo[] properties = element.GetType().GetFields(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.FlattenHierarchy);
            foreach (FieldInfo field in properties)
            {
                if (field.FieldType == typeof(DependencyProperty))
                {
                    DependencyProperty dp = (DependencyProperty)field.GetValue(null);
                    if (BindingOperations.IsDataBound(element, dp))
                    {
                        BindingExpression exp = BindingOperations.GetBindingExpression(element, dp);
                        object source = exp.DataItem;

                        if (source != null && exp != null && exp.ParentBinding != null && exp.ParentBinding.Path != null && exp.ParentBinding.Path.Path != null)
                        {
                            // get the real binding source (not the view model)
                            string path;
                            source = BindingHelpers.GetRealBindingSource(exp, source, out path);

                            // Apply EditableAttribute
                            bool? editable = AnnotationReader.GetAttributeValue<EditableAttribute, bool?>(source.GetType(), path, "AllowEdit");
                            if (editable.HasValue && editable.Value == false)
                            {
                                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_ApplyDataAnnotation, "EditableAttribute -> element.IsEnabled for \"{0}\"", TraceHelpers.GetElementName(element));
                                element.IsEnabled = false;
                            }

                            // Apply RequiredAttribute
                            bool required = AnnotationReader.GetAttributeExists<RequiredAttribute>(source.GetType(), path);
                            if (required)
                            {
                                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_ApplyDataAnnotation, "RequiredAttribute -> IsRequired for \"{0}\"", TraceHelpers.GetElementName(element));
                                SetIsRequired(element, true);
                            }

                            // Apply StringLength
                            int? stringLength = AnnotationReader.GetAttributeValue<StringLengthAttribute, int?>(source.GetType(), path, "MaximumLength");
                            if (stringLength.HasValue)
                            {
                                TextBox box = GetTextBox(element);
                                if (box != null)
                                {
                                    TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_ApplyDataAnnotation, "StringLengthAttribute -> element.MaxLength for \"{0}\"", TraceHelpers.GetElementName(element));
                                    box.MaxLength = stringLength.Value;
                                }
                            }

                            // Apply DisplayFormat
                            string formatString = AnnotationReader.GetAttributeValue<DisplayFormatAttribute, string>(source.GetType(), path, "DataFormatString");
                            if (!string.IsNullOrEmpty(formatString))
                            {
                                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_ApplyDataAnnotation, "DisplayFormatAttribute -> Binding.StringFormat for \"{0}\"", TraceHelpers.GetElementName(element));
                                BindingHelpers.ChangeBinding(element, dp, b => b.StringFormat = formatString);
                            }

                            // Apply CueBanner
                            string nullDisplayText = AnnotationReader.GetAttributeValue<DisplayFormatAttribute, string>(source.GetType(), path, "NullDisplayText");
                            if (!string.IsNullOrEmpty(nullDisplayText))
                            {
                                TraceHelpers.TraceInformation(_traceSource, TraceId.Annotations_ApplyDataAnnotation, "DisplayFormatAttribute -> CueBanner for \"{0}\"", TraceHelpers.GetElementName(element));
                                SetCueBanner(element, nullDisplayText);
                            }
                        }
                    }
                }
            }
        }
        
        public static void SetApplyDataAnnotations(UIElement element, bool value)
        {
            element.SetValue(ApplyDataAnnotationsProperty, value);
        }

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static bool GetApplyDataAnnotations(UIElement element)
        {
            return (bool)element.GetValue(ApplyDataAnnotationsProperty);
        }

        #endregion

    }
}
